%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  Copyright by Wenliang Du.                                       %%
%%  This work is licensed under the Creative Commons                %%
%%  Attribution-NonCommercial-ShareAlike 4.0 International License. %%
%%  To view a copy of this license, visit                           %%
%%  http://creativecommons.org/licenses/by-nc-sa/4.0/.              %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\newcommand{\commonfolder}{../../common-files}
\newcommand{\webcommon}{../Web_Common}

\input{\commonfolder/header}
\input{\commonfolder/copyright}

\lhead{\bfseries SEED 实验 -- SQL 注入攻击实验}

\newcommand{\sqlFigs}{./Figs}


\begin{document}


\begin{center}
{\LARGE SQL 注入攻击实验}
\end{center}

\seedlabcopyright{2006 - 2020}


% *******************************************
% SECTION
% ******************************************* 
\section{概述}

SQL 注入是一种利用 Web 应用程序与数据库交互的漏洞而进行的代码注入技术。这种漏洞是由于用户的输入在发送到后端数据库之前没有被正确检查导致的。

许多Web应用程序都会从用户那里获取输入，并使用这些输入来构造SQL查询，以便从数据库中获取信息。Web应用程序也使用 SQL 将信息存储在数据库中。这些是Web应用程序开发中的常见做法。当 SQL 的查询构建时不小心的话，可能会出现SQL注入漏洞。SQL注入是针对Web应用程序最常见的攻击之一。

在这个实验中，我们创建了一个易受 SQL 注入攻击的 Web 应用程序。学生的目标是利用程序中的漏洞来做 SQL 注入攻击，同时学习防御此类攻击的技术。本实验涵盖了以下主题：

\begin{itemize}[noitemsep]
    \item SQL 语句：SELECT 和 UPDATE 语句
    \item SQL 注入
    \item Prepared 语句 (防御方法)
\end{itemize}

\paragraph{阅读材料}
关于SQL注入的详细覆盖可以在以下章节中找到：
\begin{itemize}
    \item 《SEED书籍》中的第12章，\seedbook
\end{itemize}

\paragraph{实验环境。} 
\seedenvironmentB 
\nodependency


% *******************************************
% SECTION
% ******************************************* 
\section{实验环境}


我们为这个实验开发了一个Web应用程序，并且使用容器来搭建实验环境。实验中包含两个容器，一个用于托管Web应用程序，另一个用于托管Web应用程序的数据库。Web应用程序容器的IP地址是10.9.0.5，它的URL如下：

\begin{lstlisting}
http://www.seed-server.com
\end{lstlisting}

我们需要将此主机名映射到容器的IP地址。请在 \texttt{/etc/hosts} 文件中添加以下条目。您需要使用root权限来更改此文件（使用 \texttt{sudo}）。需要注意的是，由于某些其他实验的原因，这个名称可能已经添加到了文件中。如果它被映射到不同的IP地址，则必须删除旧的条目。

\begin{lstlisting}
10.9.0.5        www.seed-server.com
\end{lstlisting}

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{容器设置与命令}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/container/setup}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


% MySQL数据库
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\webcommon/mysql}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{关于Web应用程序} 

我们创建了一个简单的员工管理应用程序。通过此Web应用程序，员工可以查看和更新其个人资料信息。
在该Web应用程序中主要有两种角色：管理员是特权角色，可管理每个别员工的个人资料；员工是普通角色，只能查看或更新自己的个人资料信息。所有员工的信息描述见表~\ref{table:database}。


\begin{table}[htb]
\caption{数据库}
\label{table:database}
\centering
\begin{adjustbox}{max width=\textwidth}
\begin{tabular}{|l|l|l|l|l|l|l|l|l|l|l|}
\hline
%Name & Employee ID  & Password  &Salary  &Birthday  &SSN &Nickname &Email &Address &Phone\# \\
名称  & 员工ID       & 密码  &工资  &出生日期  &社会保障号 (SSN) &昵称 &邮箱 &地址 &电话 \\
\hline
Admin 	& 99999       & seedadmin  &400000  &3/5   &43254314	& & & &\\
Alice 	& 10000       & seedalice  &20000   &9/20  &10211002	& & & &\\
Boby 	& 20000       & seedboby   &50000   &4/20  &10213352	& & & &\\
Ryan    & 30000       & seedryan   &90000   &4/10  &32193525	& & & &\\
Samy 	& 40000	      & seedsamy   &40000   &1/11  &32111111 	& & & &\\
Ted     & 50000	      & seedted    &110000  &11/3  &24343244	& & & &\\
\hline
\end{tabular}
\end{adjustbox}
\end{table}
 


% *******************************************
% SECTION
% ******************************************* 
\section{实验任务}


% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务1：熟悉SQL语句}  
\label{ssec:MySQLConsole}

本任务的目标是熟悉SQL命令。我们的Web应用程序使用的数据存储在名为MySQL的数据库中，该数据库托管在我们的MySQL容器上。我们创建了一个名为 sqllab\_users 的数据库，其中包含一个名为 credential 的表。此表格存储了每个员工个人资料信息（例如 eid、密码、工资等）。在这个任务中，你需要与数据库互动以熟悉 SQL 命令。

请在 MySQL 容器中获取一个 shell（参见容器手册中的说明，手册的链接在实验网站）。然后使用 \texttt{mysql} 客户端程序来与数据库进行交互。用户名是 root，密码是 dees。

\begin{lstlisting}
// 在MySQL容器内
# mysql -u root -pdees 
\end{lstlisting}

登录后，可以创建新的数据库或加载已有的。因为我们已经创建了 sqllab\_users 数据库，你只需要使用 \texttt{use} 命令来加载它。要查看 sqllab\_users 数据库中的表，请使用 \texttt{show tables} 命令打印所选数据库中的所有表。

\begin{lstlisting}
mysql> use sqllab_users;
Database changed
mysql> show tables;
+------------------------+
| Tables_in_sqllab_users |
+------------------------+
| credential             |
+------------------------+
\end{lstlisting}

运行上述命令后，你需要使用 SQL 命令来打印员工 Alice 的所有个人资料信息。请提供你的结果截图。

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务2：在SELECT语句上执行SQL注入攻击} 

SQL 注入是一种代码注入技术，通过该技术，攻击者可以执行自己的恶意 SQL 语句（通常称为恶意负载）。通过恶意 SQL 语句，攻击者可以从受害者数据库中窃取信息，甚至更改数据库。我们的员工管理 Web 应用程序存在 SQL 注入漏洞，这些漏洞模仿了开发人员经常犯的错误。

我们将使用 \url{www.seed-server.com} 中的登录页面来完成此任务。登录页面如图 ~\ref{sql:fig:login} 所示。它要求用户提供用户名和密码。Web 应用程序根据这两条数据对用户进行身份验证，因此只有知道密码的员工才允许登录。作为攻击者，您的工作是在不知道任何员工密码的情况下登录 Web 应用程序。


\begin{figure}[htb]
    \centering
    \includegraphics[width=0.7\textwidth]{\sqlFigs/login.jpg}
    \caption{登录页面}
    \label{sql:fig:login}
\end{figure}

我们先解释一下这个 Web 应用程序是如何实现身份验证的。位于 \path{/var/www/SQL_Injection} 目录中的 PHP 代码 \texttt{unsafe\_home.php} 是用于进行用户身份验证的。以下代码片段显示了如何对用户进行身份验证。

\begin{lstlisting}
$input_uname = $_GET['username'];
$input_pwd = $_GET['Password'];
$hashed_pwd = sha1($input_pwd);
...
$sql = "SELECT id, name, eid, salary, birth, ssn, address, email,
               nickname, Password
        FROM credential
        WHERE name= '$input_uname' and Password='$hashed_pwd'";
$result = $conn -> query($sql);

// 以下是伪代码
if(id != NULL) {
  if(name=='admin') {
     return All employees information;
  } else if (name !=NULL){
    return employee information;
  }
} else {
  Authentication Fails;
}
\end{lstlisting}

上述 SQL 语句从 {\tt credential} 表中选择员工个人信息，例如 id、 name、 salary、ssn 等。该 SQL 语句使用了两个变量 \texttt{input\_uname} 和 \texttt{hashed\_pwd}，其中 \texttt{input\_uname} 保存着用户在登录页面的用户名字段中输入的信息，而 \texttt{hashed\_pwd} 保存着用户输入的密码的 \texttt{sha1} 哈希值。程序检查数据库中是否有记录与提供的用户名和密码匹配，如果是的话，那么用户身份验证就成功了，相应的员工信息会提供给用户。如果没有记录匹配，则身份验证失败。

\paragraph{任务 2.1：网页上的 SQL 注入攻击。}
您的任务是从登录页面以管理员身份登录 Web 应用程序，以便查看所有员工的信息。我们假设您知道管理员的帐户名称，即 {\tt admin}，但不知道密码。您需要决定在 \texttt{用户名} 和 \texttt{密码} 字段中输入什么才能成功登录。

\paragraph{任务 2.2：从命令行进行 SQL 注入攻击。}
您的任务是重复任务 2.1，但您需要在不使用网页的情况下执行此操作。您可以使用命令行工具，例如 \texttt{curl}，它可以发送 HTTP 请求。值得一提的是，如果您想在 HTTP 请求中包含多个参数，则需要将 URL 和参数放在一对单引号之间，否则，用于分隔参数的特殊字符（例如 \texttt{\&}）将被 shell 程序解释，从而改变命令的含义。以下示例显示如何向我们的 Web 应用程序发送 HTTP GET 请求，并附加两个参数（\texttt{username} 和 \texttt{Password}）：

\begin{lstlisting}
$ curl 'www.seed-server.com/unsafe_home.php?username=alice&Password=11'
\end{lstlisting}

如果你需要在用户名或密码字段中包含特殊字符，你需要对它们进行编码，否则，这些特殊字符可以改变你的请求的意义。如果你想在这些字段中包含单引号，你应该使用 \%27 代替；如果要包含空格，则应使用 \%20。在这个任务中，在发送请求时你需要处理好 HTTP 编码。

\paragraph{任务2.3：附加一个新的SQL语句}
在这两个攻击中，我们只能从数据库中窃取信息，如果有办法可以利用这个登录页面的漏洞修改数据库将会更好。一个想法是使用 SQL 注入攻击注入两条 SQL 语句，第二条可以是更新或删除语句。在 SQL 中，分号（;）用于分隔两条 SQL 语句，请尝试通过登录页面运行两条 SQL 语句。

系统里有一个防范措施会防止你运行两条 SQL 语句，所以你的攻击不会成功。请查阅网络资源找出这种防范措施是什么，并将你的发现记录在实验报告中。


% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务3：在UPDATE语句上执行SQL注入攻击} 

如果一个 SQL 注入漏洞出现在 UPDATE 语句上，损害将会更大，因为攻击者可以利用这个漏洞修改数据库。我们的员工管理应用程序有一个允许员工更新其个人资料信息（包括昵称、电子邮件、地址、电话号码和密码）的页面（参见附图~\ref{sql:fig:edit}）。要访问此页面，员工需要先登录。

当员工通过这个编辑资料页面来更新他们的信息时，以下 SQL UPDATE 查询将被执行，这个代码在 \texttt{unsafe\_edit\_backend.php} 文件中，位于 /var/www/SQLInjection 目录下。

\begin{lstlisting}
$hashed_pwd = sha1($input_pwd);
$sql = "UPDATE credential SET
	nickname='$input_nickname',
	email='$input_email',
	address='$input_address',
	Password='$hashed_pwd',
	PhoneNumber='$input_phonenumber'
	WHERE ID=$id;";
$conn->query($sql);
\end{lstlisting}

\begin{figure}[htb]
    \centering
    \includegraphics[width=0.6\textwidth]{\sqlFigs/editprofile.jpg}
    \caption{编辑资料页面}
    \label{sql:fig:edit}
\end{figure}

\paragraph{任务3.1：修改自己的工资}  
如图所示，员工只能更新他们的昵称、电子邮件、地址、电话号码和密码，他们没有资格更改自己的工资。假设你是Alice，你不满你的老板 Boby 今年没有给你加工资。你想利用编辑资料的 SQL 注入漏洞来增加自己的工资，请演示如何实现这一点。我们假定你知道工资在数据库表格中的列名是 salary。

\paragraph{任务3.2：修改其他人的工资}  
在增加了自己的工资之后，你决定惩罚你的老板 Boby。你想把他的工资减少到1美元。请演示如何实现这一点。

\paragraph{任务3.3：修改其他人的密码}
在更改完 Boby 的工资后，你依然不满，所以想把Boby的密码改成你知道的内容，然后你就可以登录到他的账号。请演示如何实现这一点。需要注意的是，数据库中存储的是密码的哈希值而不是明文密码字符串。你可以再次查看 \texttt{unsafe\_edit\_backend.php} 代码以了解密码是如何被存储的，它使用 SHA1 哈希函数生成密码的哈希值。

\subsection{任务4：防范措施 --- Prepared 语句} 

SQL注入漏洞的根本问题是未能将代码与数据区分开来。当在 Web 应用服务器端构建 SQL 语句时，程序员是知道哪一部分是数据哪一部分是代码，但在将 SQL 语句发送到数据库后，原来的界限已经消失，SQL 解释器看到的数据和代码的界限可能与开发人员设置的原始界限不同。为了解决这个问题，确保服务器端和数据库端看到的界限是一致至关重要。最安全的方法是使用 \textit{Prepared 语句}。

\begin{figure}
    \centering
    \includegraphics[width=0.8\textwidth]{\sqlFigs/PreparedStatement.pdf}
    \caption{Prepared 语句的工作流程}
    \label{sql:fig:preparedstatement}
\end{figure}

为了了解 Prepared 语句如何防止 SQL 注入，我们需要理解当 SQL 服务器收到 SQL 语句时会发生什么。图~\ref{sql:fig:preparedstatement} 展示了 SQL 语句执行的工作流程。在编译步骤中，语句首先通过解析和规范化阶段，检查其语法和语义。接下来是编译阶段，在这个阶段，关键字（如 SELECT、FROM、UPDATE 等）被转换成机器可理解的格式。基本上，在此阶段，语句就被解释了。在优化阶段会考虑执行语句的不同方案，并从中选择最佳的优化方案。所选的计划被缓存存储起来，当下一个语句进来时，它将与缓存中的内容进行比较，如果已经在缓存中，则可以跳过解析、编译和查询优化阶段，直接将已编译的语句交给执行阶段运行。

Prepared 语句不是一个完整的 SQL 语句，它的一些数据部分是空着的，需要在后面来填充。Prepared 语句会经过编译步骤转换成预编译的语句。要运行这个预编译语句，需要填充缺的数据，但是这些数据不会再经历编译步骤，它们将被直接插入到预编译语句中，交给执行引擎。因此，即使数据中包含 SQL 代码，这些代码也不会经过编译，它们会被当做数据，没有任何特殊意义。这就是 Prepared 语句为什么能防止 SQL 注入攻击的原理。

下面是一个使用 Prepared 语句的例子。我们使用 SELECT 语句作为示例，并展示如何使用 Prepared 语句来重写 SQL 代码。

\begin{lstlisting}
$sql = "SELECT name, local, gender  
        FROM USER_TABLE 
        WHERE id = $id AND password ='$pwd' ";
$result = $conn->query($sql)
\end{lstlisting}

上述代码存在 SQL 注入漏洞。可以将其重写为以下形式

\begin{lstlisting}
$stmt = $conn->prepare("SELECT name, local, gender
                        FROM USER_TABLE 
                        WHERE id = ? and password = ? ");
// 绑定参数
$stmt->bind_param("is", $id, $pwd);
$stmt->execute();
$stmt->bind_result($bind_name, $bind_local, $bind_gender);
$stmt->fetch();
\end{lstlisting}

使用 Prepared 语句，我们把发送 SQL 语句到数据库的过程分为两步。第一步是仅发送代码部分，即不包含实际数据。这是编译步骤。如上例所示，实际的数据被占位符（?）替换。在该步骤之后，我们再通过 \texttt{bind\_param()} 发送数据给数据库。数据库把此步骤中发送的一切仅视为数据而不再将其视为代码。它将这些数据绑定到编译好的 Prepared 语句中的相应占位符上。在 \texttt{bind\_param()} 方法中，第一个参数 {\tt "is"} 指示了参数的类型：\texttt{"i"} 表明 \texttt{\$id} 中的数据为整数类型，而 \texttt{"s" } 则表示 \texttt{\$pwd} 中的数据为字符串类型。

\paragraph{任务。} 在这个任务中，我们将使用 Prepared 语句来修复 SQL 注入漏洞。为了简化起见，在 defense 文件夹中我们创建了一个简化的程序。你需要对这个文件夹中的文件进行修改。如果你将浏览器指向以下 URL，则会看到类似于 Web 应用程序登录页面的页面。该页面允许你查询员工的信息，但需要提供正确的用户名和密码。

\begin{lstlisting}
URL: http://www.seed-server.com/defense/
\end{lstlisting}

此页中输入的数据将被发送到名为 getinfo.php 的服务器程序，该程序会调用名为 unsafe.php 的程序。这个 PHP 程序中的 SQL 查询存在 SQL 注入漏洞。你的任务是用 Prepared 语句修改 unsafe.php 中的 SQL 语句，从而使程序能够防范 SQL 注入攻击。在实验设置文件夹中，unsafe.php 程序位于 image\_www/Code/defense 文件夹内。你可以直接在那里修改该程序。完成后，你需要重建并重启容器，否则更改将不会生效。你也可以通过 \texttt{"docker cp"} 命令将文件复制到正在运行的容器中。

你还可以在正在运行的容器内部修改此文件。在这个容器内，unsafe.php 程序位于 \path{/var/www/SQL_Injection/defense} 文件夹下。为了保持 Docker 镜像较小，我们在容器里仅安装了一个非常简单的文本编辑器 nano。对于简单的编辑来说应该足够了。如果你不喜欢这个编辑器，你也可以通过执行以下命令来安装其它命令行编辑器，例如 vim：

\begin{lstlisting}
# apt install -y vim 
\end{lstlisting}

此安装将在容器关闭并销毁后被丢弃。如果你想让其永久化，则可以将安装命令添加到 image\_www 文件夹内的 Dockerfile 中。


\section{指南}
\label{sec:guidelines}

\paragraph{测试 SQL 注入字符串。}
在实际应用中，很难检查你的 SQL 注入攻击是否包含任何语法错误，因为通常服务器不会返回此类错误消息。为了进行调查，你可以将 PHP 源代码中的 SQL 语句复制到 MySQL 控制台。假设你有以下 SQL 语句，并且注入字符串是 {\tt ' or 1=1;\#}。

\begin{lstlisting}
SELECT * from credential 
WHERE name='$name' and password='$pwd';
\end{lstlisting}

你可以将 \texttt{\$name} 的值替换为注入字符串，然后在 MySQL 控制台中测试它。这种方法可以帮助你在实际攻击之前构建一个没有语法错误的注入字符串。

% *******************************************
% SECTION
% ******************************************* 
\section{提交}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/submission}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


\end{document}


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% 以下部分被移除
\begin{comment}
\paragraph{使用 magic\_quotes\_gpc 转义特殊字符}

你将会知道SQL注入攻击是可能的，因为攻击者可以利用某些特殊字符来改变现有的SQL查询。在 PHP 代码中，如果一个数据变量是字符串类型，则需要在其前后各加上一对单引号（'）。例如，在上面列出的 SQL 查询中我们有 name = '\$user'。这一对引号实际上试图将 \texttt{\$user} 变量中的数据与代码区分开来。
不幸的是，如果 \texttt{\$user} 变量的内容包含任何单引号，则这种分离将失败。因此，我们需要一种机制告诉数据库 \texttt{\$user} 中的单引号应该被当作数据的一部分而不是SQL中的特殊字符。我们只需在单引号之前加上一个反斜杠（\textbackslash）即可防止改变任何现有 SQL 查询。
PHP 提供了一种机制自动在用户输入中为这些字符添加反斜杠（'，"，\textbackslash 和 NULL）。如果此功能开启，则所有这些字符都会被自动转义。这种机制被称为magic quotes并通常由 \texttt{magic_quotes_gpc} 的值表示。

请注意，magic\_quotes\_gpc 功能自 5.3.0 版本已 DEPRECATE，并且从 PHP 5.4.0 版本起已被 REMOVED。安装在 SEEDUbuntu VM 中的 PHP 版本为 5.3.5，因此你仍然可以玩一下这个功能。
以下是将其移除的原因：

\begin{itemize}
    \item 可移植性：假设其为开启或关闭将影响可移植性。大多数代码都必须使用一个名为 get\_magic\_quotes\_gpc() 的函数来检查此情况并相应地进行处理。

    \item 性能和不便之处：并非所有用户输入都会用于 SQL 查询，因此强制转义所有数据不仅会影响性能，
      而且在某些不应被转义的数据上也会显得令人厌烦。
\end{itemize}

\end{comment}
